
Imports System.Threading
Imports std
Imports storoomc

Public Class storserver
    Protected WithEvents server As tcpServer2 = Nothing
    Protected slices As storageInterface = Nothing
    Private lockwrite As Boolean = False
    Private lockread As Boolean = False

    Shared Sub New()
        Dim vs As vector(Of String) = Nothing
        vs = New vector(Of String)()
        For Each s As Reflection.FieldInfo In
            GetType(protocol.storserver.storserver).GetFields(Reflection.BindingFlags.Public Or
                                                              Reflection.BindingFlags.Static)
            vs.push_back(s.GetValue(Nothing))
        Next

        For i As Int64 = 0 To vs.size() - 2
            For j As Int64 = i + 1 To vs.size() - 1
                assert(Not strsame(vs(i), vs(j), False))
            Next
        Next
    End Sub

    Private Sub shutdown()
        If Not server Is Nothing Then
            server.stop()
            server = Nothing
        End If
    End Sub

    Protected Overridable Function serverport() As Int32
        Return 0
    End Function

    Public Sub [stop]()
        raiseError("storserver.server will be stop, waiting for all the connections to be shutdown...")
        shutdown()
    End Sub

    Protected Sub start(ByVal port As Int32)
        assert(Not server Is Nothing, "storserver.server is nothing before start.")
        If server.active() Then
            server.stop()
        End If

        server.port = port
        raiseError("storserver.server will be started on port " + Convert.ToString(server.port))
        If Not server.start() Then
            raiseError("storserver.server failed, start shutdown...", errorHandle.errorType.system)
            shutdown()
        End If
    End Sub

    Public Sub start()
        start(serverport())
    End Sub

    Public Function health() As Boolean
        Return Not server Is Nothing AndAlso server.health()
    End Function

    Protected Sub senddataInsertErrorHandle(ByVal e As String)
        raiseError("cannot insert data to senddata, " + e, errorHandle.errorType.exclamation, , , 1)
    End Sub

    Private _true As _do(Of Boolean) = _
        Function() As Boolean
            Return True
        End Function

    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Private Function handle(ByVal senddata As nettransData, _
                            ByVal work As _do(Of Boolean), _
                            ByVal rtn As _do(Of Boolean)) As Boolean
        assert(Not work Is Nothing)
        assert(Not rtn Is Nothing)
        If work() Then
            If Not senddata.insert(protocol.success) Then
                senddataInsertErrorHandle("cannot insert protocol.success when " + callingcode(1))
                Return False
            End If
            If Not rtn() Then
                senddataInsertErrorHandle("cannot insert result when " + callingcode(1))
                Return False
            End If
        Else
            If Not senddata.insert(protocol.fail) Then
                senddataInsertErrorHandle("cannot insert protocol.fail when " + callingcode(1))
                Return False
            End If
        End If

        Return True
    End Function

    Private Function capacity(ByVal senddata As nettransData) As Boolean
        Dim cap As UInt64
        Dim work As _do(Of Boolean) = _
            Function() As Boolean
                Return slices.capacity(cap)
            End Function
        Dim rtn As _do(Of Boolean) = _
            Function() As Boolean
                Return senddata.insert(Convert.ToString(cap))
            End Function
        Return handle(senddata, work, rtn)
    End Function

    Private Function valuesize(ByVal senddata As nettransData) As Boolean
        Dim size As UInt64
        Dim work As _do(Of Boolean) = _
            Function() As Boolean
                Return slices.valuesize(size)
            End Function
        Dim rtn As _do(Of Boolean) = _
            Function() As Boolean
                Return senddata.insert(Convert.ToString(size))
            End Function
        Return handle(senddata, work, rtn)
    End Function

    Private Function delete(ByVal cmd As String, _
                            ByVal receiveData As nettransData, ByRef receiveDataIndex As Int64, _
                            ByVal senddata As nettransData) As Boolean
        If receiveData.fieldCount < receiveDataIndex + 1 Then
            raiseError("receiveData.fieldCount < receiveDataIndex + 1 when delete / tsDelete.", _
                       errorHandle.errorType.user)
            Return False
        End If
        Dim key As String = Nothing
        key = receiveData.take(Of String)(inc_(receiveDataIndex))

        Dim work As _do(Of Boolean) = _
            Function() As Boolean
                Return Not key Is Nothing AndAlso _
                       ((cmd = protocol.storserver.delete AndAlso _
                         slices.delete(key)) OrElse _
                        (cmd = protocol.storserver.tsDelete AndAlso _
                         slices.delete(timestampkey(key)) AndAlso slices.delete(datakey(key))))
            End Function
        Return handle(senddata, work, _true)
    End Function

    Private Function wm(ByVal cmd As String, _
                        ByVal receiveData As nettransData, ByRef receiveDataIndex As Int64, _
                        ByVal senddata As nettransData) As Boolean
        If receiveData.fieldCount < receiveDataIndex + 2 Then
            raiseError("receiveData.fieldCount < receiveDataIndex + 2 when write / modify / uniqueWrite.", _
                       errorHandle.errorType.user)
            Return False
        End If
        Dim key As String = Nothing
        key = receiveData.take(Of String)(inc_(receiveDataIndex))
        Dim buff As bytes = Nothing
        buff = receiveData.take(inc_(receiveDataIndex))

        Dim work As _do(Of Boolean) = _
            Function() As Boolean
                Return Not lockwrite AndAlso
                       Not key Is Nothing AndAlso Not buff Is Nothing AndAlso _
                       ((cmd = protocol.storserver.write AndAlso slices.write(key, buff)) OrElse _
                        (cmd = protocol.storserver.modify AndAlso slices.modify(key, buff)) OrElse _
                        (cmd = protocol.storserver.uniqueWrite AndAlso slices.uniqueWrite(key, buff)))
            End Function
        Return handle(senddata, work, _true)
    End Function

    Private Function tswm(ByVal cmd As String, _
                          ByVal receiveData As nettransData, ByRef receiveDataIndex As Int64, _
                          ByVal senddata As nettransData) As Boolean
        If receiveData.fieldCount < receiveDataIndex + 3 Then
            raiseError("receiveData.fieldCount < receiveDataIndex + 3 when tsWrite / tsModify.", _
                       errorHandle.errorType.user)
            Return False
        End If
        Dim key As String = Nothing
        key = receiveData.take(Of String)(inc_(receiveDataIndex))
        Dim dbuff As bytes = Nothing
        dbuff = receiveData.take(inc_(receiveDataIndex))
        Dim tbuff As bytes = Nothing
        tbuff = receiveData.take(inc_(receiveDataIndex))

        Dim work As _do(Of Boolean) = _
            Function() As Boolean
                Return Not lockwrite AndAlso
                       Not key Is Nothing AndAlso Not dbuff Is Nothing AndAlso Not tbuff Is Nothing AndAlso _
                       ((cmd = protocol.storserver.tsWrite AndAlso slices.write(datakey(key), dbuff)) OrElse _
                        (cmd = protocol.storserver.tsModify AndAlso slices.modify(datakey(key), dbuff))) AndAlso _
                       slices.modify(timestampkey(key), tbuff)
            End Function
        Return handle(senddata, work, _true)
    End Function

    Private Function read(ByVal receiveData As nettransData, ByRef receiveDataIndex As Int64, _
                          ByVal senddata As nettransData) As Boolean
        If receiveData.fieldCount < receiveDataIndex + 1 Then
            raiseError("receiveData.fieldCount < receiveDataIndex + 1 when read, no key available.", _
                       errorHandle.errorType.user)
            Return False
        End If

        Dim key As String = Nothing
        key = receiveData.take(Of String)(inc_(receiveDataIndex))
        Dim buff As bytes = Nothing

        Dim work As _do(Of Boolean) = _
            Function() As Boolean
                Return Not lockread AndAlso Not key Is Nothing AndAlso slices.read(key, buff)
            End Function
        Dim rtn As _do(Of Boolean) = _
            Function() As Boolean
                Return senddata.insert(buff)
            End Function
        Return handle(senddata, work, rtn)
    End Function

    Private Function tsread(ByVal receiveData As nettransData, ByRef receiveDataIndex As Int64, _
                            ByVal senddata As nettransData) As Boolean
        If receiveData.fieldCount < receiveDataIndex + 1 Then
            raiseError("receiveData.fieldCount < receiveDataIndex + 1 when tsRead, no key available.", _
                       errorHandle.errorType.user)
            Return False
        End If
        Dim key As String = Nothing
        key = receiveData.take(Of String)(inc_(receiveDataIndex))
        Dim dbuff As bytes = Nothing
        Dim tbuff As bytes = Nothing

        Dim work As _do(Of Boolean) = _
            Function() As Boolean
                Return Not lockread AndAlso Not key Is Nothing AndAlso _
                       slices.read(datakey(key), dbuff) AndAlso slices.read(timestampkey(key), tbuff)
            End Function
        Dim rtn As _do(Of Boolean) = _
            Function() As Boolean
                Return senddata.insert(dbuff) AndAlso senddata.insert(tbuff)
            End Function
        Return handle(senddata, work, rtn)
    End Function

    Private Function seek(ByVal cmd As String, _
                          ByVal receiveData As nettransData, ByRef receiveDataIndex As Int64, _
                          ByVal senddata As nettransData) As Boolean
        If receiveData.fieldCount < receiveDataIndex + 1 Then
            raiseError("receiveData.fieldCount < receiveDataIndex + 1 when seek / tsSeek.", errorHandle.errorType.user)
            Return False
        End If
        Dim key As String = Nothing
        key = receiveData.take(Of String)(inc_(receiveDataIndex))

        Dim work As _do(Of Boolean) = _
            Function() As Boolean
                Return Not lockread AndAlso Not key Is Nothing AndAlso _
                       ((cmd = protocol.storserver.seek AndAlso _
                         slices.seek(key) OrElse _
                        (cmd = protocol.storserver.tsSeek AndAlso _
                         slices.seek(datakey(key)) AndAlso slices.seek(timestampkey(key)))))
            End Function
        Return handle(senddata, work, _true)
    End Function

    Private Function list(ByVal cmd As String, ByVal senddata As nettransData) As Boolean
        Dim keys As vector(Of String) = Nothing
        Dim work As _do(Of Boolean) = _
            Function() As Boolean
                Dim t As vector(Of String) = Nothing
                If slices.list(t) Then
                    If cmd = protocol.storserver.list Then
                        keys = t
                    ElseIf cmd = protocol.storserver.tsList Then
                        keys = New vector(Of String)()
                        Dim i As Int64
                        For i = 0 To t.size() - 1
                            If isdatakey(t(i)) Then
                                keys.push_back(originalkey(t(i)))
                            End If
                        Next
                    Else
                        assert(False, "should cover all situations.")
                    End If
                    Return True
                Else
                    Return False
                End If
            End Function
        Dim rtn As _do(Of Boolean) = _
            Function() As Boolean
                If Not senddata.insert(Convert.ToString(keys.size())) Then
                    Return False
                End If
                Dim i As Int64
                For i = 0 To keys.size() - 1
                    If Not senddata.insert(keys(i)) Then
                        Return False
                    End If
                Next
                Return True
            End Function
        Return handle(senddata, work, rtn)
    End Function

    Private Function status(ByVal senddata As nettransData) As Boolean
        Dim allcount As UInt64
        Dim unhealthcount As UInt64
        Dim work As _do(Of Boolean) = _
            Function() As Boolean
                Return slices.status(allcount, unhealthcount)
            End Function
        Dim rtn As _do(Of Boolean) = _
            Function() As Boolean
                Return senddata.insert(Convert.ToString(allcount)) AndAlso _
                    senddata.insert(Convert.ToString(unhealthcount))
            End Function
        Return handle(senddata, work, rtn)
    End Function

    Private Function open(ByVal senddata As nettransData) As Boolean
        Dim work As _do(Of Boolean) = _
            Function() As Boolean
                Return slices.open()
            End Function
        Return handle(senddata, work, _true)
    End Function

    Private Function close(ByVal senddata As nettransData) As Boolean
        Dim work As _do(Of Boolean) = _
            Function() As Boolean
                Return slices.close()
            End Function
        Return handle(senddata, work, _true)
    End Function

    Private Function health(ByVal senddata As nettransData) As Boolean
        Dim work As _do(Of Boolean) = _
            Function() As Boolean
                Return (Not lockread OrElse Not lockwrite) AndAlso slices.health()
            End Function
        Return handle(senddata, work, _true)
    End Function

    Private Function full(ByVal senddata As nettransData) As Boolean
        Dim work As _do(Of Boolean) = _
            Function() As Boolean
                Return slices.full()
            End Function
        Return handle(senddata, work, _true)
    End Function

    Private Function retire(ByVal senddata As nettransData) As Boolean
        Dim work As _do(Of Boolean) = _
            Function() As Boolean
                Return slices.retire()
            End Function
        Return handle(senddata, work, _true)
    End Function

    Private Function empty(ByVal senddata As nettransData) As Boolean
        Dim work As _do(Of Boolean) = _
            Function() As Boolean
                Return slices.empty()
            End Function
        Return handle(senddata, work, _true)
    End Function

    Private Function sizeof(ByVal cmd As String, _
                            ByVal receiveData As nettransData, ByRef receiveDataIndex As Int64, _
                            ByVal senddata As nettransData) As Boolean
        If receiveData.fieldCount < receiveDataIndex + 1 Then
            raiseError("receiveData.fieldCount < receiveDataIndex + 1 when get sizeof / tsSizeof.", _
                       errorHandle.errorType.user)
            Return False
        End If
        Dim key As String = Nothing
        key = receiveData.take(Of String)(inc_(receiveDataIndex))
        Dim size As UInt64

        Dim work As _do(Of Boolean) = _
            Function() As Boolean
                Return Not lockread AndAlso Not key Is Nothing AndAlso _
                       ((cmd = protocol.storserver.sizeof AndAlso _
                         slices.sizeof(key, size)) OrElse _
                        (cmd = protocol.storserver.tsSizeof AndAlso _
                         slices.sizeof(datakey(key), size)))
            End Function
        Dim rtn As _do(Of Boolean) = _
            Function() As Boolean
                Return senddata.insert(Convert.ToString(size))
            End Function
        Return handle(senddata, work, rtn)
    End Function

    Private Function keycount(ByVal cmd As String, ByVal senddata As nettransData) As Boolean
        Dim count As UInt64
        Dim work As _do(Of Boolean) = _
            Function() As Boolean
                Return slices.keycount(count)
            End Function
        Dim rtn As _do(Of Boolean) = _
            Function() As Boolean
                Return senddata.insert(Convert.ToString( _
                                       ifoper(cmd = protocol.storserver.tsKeycount, (count >> 1), count)))
            End Function
        Return handle(senddata, work, rtn)
    End Function

    Private Function sync(ByVal receiveData As nettransData, ByRef receiveDataIndex As Int64, _
                          ByVal senddata As nettransData) As Boolean
        If receiveData.fieldCount < receiveDataIndex + 2 Then
            raiseError("receiveData.fieldCount < receiveDataIndex + 2 when get sync.", errorHandle.errorType.user)
            Return False
        End If
        Dim desthost As String = Nothing
        desthost = receiveData.take(Of String)(inc_(receiveDataIndex))
        Dim destportStr As String = Nothing
        destportStr = receiveData.take(Of String)(inc_(receiveDataIndex))

        Dim work As _do(Of Boolean) = _
            Function() As Boolean
                Dim destport As Int32 = 0
                If Not convertor.toint32(destportStr, destport) Then
                    Return False
                End If
                Return slices.sync(desthost, destport)
            End Function
        Return handle(senddata, work, _true)
    End Function

    Private Function version(ByVal senddata As nettransData) As Boolean
        If senddata.insert(buildInfo2) Then
            Return True
        Else
            senddataInsertErrorHandle("cannot insert buildInfo2 when get version.")
            Return False
        End If
    End Function

    Private Function unknown(ByVal senddata As nettransData) As Boolean
        If senddata.insert(protocol.unknown) Then
            Return True
        Else
            senddataInsertErrorHandle("cannot insert protocol.unknown.")
            Return False
        End If
    End Function

    Private Function lock(ByVal senddata As nettransData, ByVal read As Boolean, ByVal write As Boolean) As Boolean
        If read Then
            lockread = True
        End If
        If write Then
            lockwrite = True
        End If
        If senddata.insert(protocol.success) Then
            Return True
        Else
            senddataInsertErrorHandle("cannot insert protocol.success when lock")
            Return False
        End If
    End Function

    Private Function unlock(ByVal senddata As nettransData, ByVal read As Boolean, ByVal write As Boolean) As Boolean
        If read Then
            lockread = False
        End If
        If write Then
            lockwrite = False
        End If
        If senddata.insert(protocol.success) Then
            Return True
        Else
            senddataInsertErrorHandle("cannot insert protocol.success when unlock")
            Return False
        End If
    End Function

    Private Function handleCommand(ByVal cmd As String, _
                                   ByVal receiveData As nettransData, ByRef receiveDataIndex As Int64, _
                                   ByVal senddata As nettransData) As Boolean
        'though storoom/stornode can handle nothing key & buff, still check here to save a call.
        'and can think storserver does not accept invalid input.
        Select Case cmd
            Case protocol.storserver.capacity
                Return capacity(senddata)
            Case protocol.storserver.valuesize
                Return valuesize(senddata)
            Case protocol.storserver.delete, protocol.storserver.tsDelete
                Return delete(cmd, receiveData, receiveDataIndex, senddata)
            Case protocol.storserver.write, protocol.storserver.modify, protocol.storserver.uniqueWrite
                Return wm(cmd, receiveData, receiveDataIndex, senddata)
            Case protocol.storserver.tsWrite, protocol.storserver.tsModify
                Return tswm(cmd, receiveData, receiveDataIndex, senddata)
            Case protocol.storserver.read
                Return read(receiveData, receiveDataIndex, senddata)
            Case protocol.storserver.tsRead
                Return tsread(receiveData, receiveDataIndex, senddata)
            Case protocol.storserver.seek, protocol.storserver.tsSeek
                Return seek(cmd, receiveData, receiveDataIndex, senddata)
            Case protocol.storserver.list, protocol.storserver.tsList
                Return list(cmd, senddata)
            Case protocol.storserver.status
                Return status(senddata)
            Case protocol.storserver.open
                Return open(senddata)
            Case protocol.storserver.close
                Return close(senddata)
            Case protocol.storserver.health
                Return health(senddata)
            Case protocol.storserver.full
                Return full(senddata)
            Case protocol.storserver.retire
                Return retire(senddata)
            Case protocol.storserver.empty
                Return empty(senddata)
            Case protocol.storserver.sizeof, protocol.storserver.tsSizeof
                Return sizeof(cmd, receiveData, receiveDataIndex, senddata)
            Case protocol.storserver.keycount, protocol.storserver.tsKeycount
                Return keycount(cmd, senddata)
            Case protocol.storserver.sync
                Return sync(receiveData, receiveDataIndex, senddata)
            Case protocol.storserver.version
                Return version(senddata)
            Case protocol.storserver.lock
                Return lock(senddata, True, True)
            Case protocol.storserver.unlock
                Return unlock(senddata, True, True)
            Case protocol.storserver.lockread
                Return lock(senddata, True, False)
            Case protocol.storserver.unlockread
                Return unlock(senddata, True, False)
            Case protocol.storserver.lockwrite
                Return lock(senddata, False, True)
            Case protocol.storserver.unlockwrite
                Return unlock(senddata, False, True)
            Case Else
                Return unknown(senddata)
        End Select
    End Function

    Private Sub server_receive(ByVal handle As tcpServer2.talkHandle) Handles server.receive
        assert(Not handle Is Nothing, "handle is nothing when server_receive.")
        Dim receiveData As nettransData = handle.receiveData
        If receiveData.fieldCount <= 0 Then
            raiseError("receiveData.fieldCount <= 0, receive empty data, ignore request.", _
                       errorHandle.errorType.warning)
            Exit Sub
        End If

        Dim receiveDataIndex As Int64 = 0
        Dim senddata As nettransData = Nothing
        senddata = New nettransData()

        While receiveDataIndex < receiveData.size()
            Dim cmd As String = Nothing
            cmd = receiveData.take(Of String)(inc_(receiveDataIndex))
            If isemptystring(cmd) Then
                raiseError("cmd is nothing or emptystring.", errorHandle.errorType.warning)
            Else
                cmd = strtolower(cmd)
                If isdebugbuild() Then
                    raiseError("receive command " + cmd + " from " + handle.remoteAddress + ":" _
                               + Convert.ToString(handle.remotePort))
                End If
                If Not handleCommand(cmd, receiveData, receiveDataIndex, senddata) Then
                    senddata.insert(protocol.fail)
                End If
            End If
        End While

        If Not handle.return(senddata) Then
            raiseError("cannot reply data to client " + displayHostPort(handle.remoteAddress(), handle.remotePort()) + _
                       ", data = " + senddata.display(), errorHandle.errorType.system)
        End If
    End Sub

    Public Sub New()
        If Not server Is Nothing Then
            server.stop()
        Else
            server = New tcpServer2()
        End If
        server.holdtime = talkTime.room2node.holdtime
        server.livetime = talkTime.room2node.livetime
    End Sub
End Class
